Learn RxSwift

Rx 的世界基本上是由下述二種物件所組成。

  • Observable (Sequence): 發出事件的對象
  • Observer: 收到事件要處理的對象

而這些物件再透過 transform, filter, combine 共同組合強大的功能。

Rx 介紹

Observable

常用到的 Observable 建立:

  • empty()
  • just()
  • from()
  • create()
1
2
3
4
5
let s = Observable<Int>.create { observer in
observer.onNext(3)
return AnonymousDisposable {
}
}

Subject

是 Observable 也是 Observer,常用的有下述:

  • PublishSubject: 訂閱後進行發送
  • ReplaySubject: 訂閱後會補訂閱前發送的事件,有 size 限制
  • BehaviorSubject: 給定一個預設值,訂閱後會補最後一個事件,如果沒有最後的事件則發送預設值

Transformer

轉換,將一個 Sequence 轉成另一個 Sequence

  • map:將傳入的參數做 一次轉換, input 和 output 的資料型別不同
1
2
3
4
5
6
7
Observable.of(1, 2, 3)
.map { int in // 代入 int
"test: \(int)" // 回傳 String
}
.subscribeNext {
print( ($0))
}
  • flatMap:將傳入的參數轉成 Observable 再傳出去,假設有一個二階的資料,先傳入第一階的資料,然後在 flatMap 中把第二階的資料作為 Observable 回傳,如此一來就會把二階的資料 拍平 成一階

Filter

  • distinctUntilChanged: 忽略與上次的觸發事件相同的值,如果這個事件是比較花費資源的計算建議要加入該 filter
1
2
3
4
5
6
7
8
let inputValid = textField.rx_text
.map { s in
s.characters.count > 5
}
.distinctUntilChanged()
.subscribeNext { b in
print(b)// 只有值改變時才會執行這裡
}

Combination

  • combineLatest:同時監看多個 sequence, 當其中一個 sequence 觸發時將這些 sequence 合併送出
1
2
3
4
5
6
let s = Observable<String>.combineLatest(textField1.rx_text, textField2.rx_text) {
s1, s2 in
return "value1: \(s1), value2: \(s2)"
}
.bindTo(label1.rx_text)
.addDisposableTo(disposeBag)

UI interactive

一個 UI control 要綁定一個 observable 時:

1
observable.bindTo(ctrl.rx_xxx)

當 model 資料變更時要觸發 View 更新:

1
observable.onNext(xxx)

UIButton 按下時,讀入 UITextField 內的值進行處理

簡單版:

1
2
3
4
5
6
7
8
9
10
11
12
let allText = Observable.combineLatest(textField1.rx_text, textField2.rx_text) {
text1, text2 in
return("all text:\(text1) + \(text2)")
}
.addDisposableTo(disposeBag)

runButton.rx_tap
.flatMapLatest {
return allText
}
.bindTo(testLabel.rx_text)
.addDisposableTo(disposeBag)

MVVM 版:

ViewModel:

1
2
3
4
5
6
7
8
9
10
11
12
class ViewModel {
var disposeBag = DisposeBag()
init(text: Observable<String>, buttonTaps: Observable<Void>) {
buttonTaps
.flatMapLatest { // 回傳 UITextField 的 rx_text 進行處理
return text.take(1)
}
.subscribeNext { text in
// do something
}
.addDisposeTo(disposeBag)
}

ViewController:

1
2
3
4
5
6
7
8
9
class ViewController {
var textField: UITextField!
var runButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()

let viewModel = ViewModel(textField.rx_text, runButton.rx_tap)
}
}

一個服務在背景運作,並持續透過一個 callback (closure) 發出命令,可以作一個 Rx 的 Wrapper ,更可以直接綁定在 UI 上

ViewModel:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class TestViewModel {
func progress(closure: (String) -> Void) -> Self {
dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INTERACTIVE.rawValue), 0)) {

for i in 0..<100 {
closure("the value: \(i)")
sleep(1)
}

}
return self
}
}

extension TestViewModel {
func rx_progress() -> Observable<String> {
return Observable.create { observer in
self.progress() { s in
dispatch_async(dispatch_get_main_queue()) {
observer.onNext(s)
}
}
return AnonymousDisposable {
}
}
}
}

ViewController:

1
2
3
4
5
var test = TestViewModel()
test.rx_progress()
.observeOn(MainScheduler.instance)
.bindTo(label1.rx_text)
.addDisposableTo(disposeBag)

綁定一個文字方塊,為了避免用戶輸入時一直拿到值進行運行,可以透過 debounce(throttle) 來處理

1
2
3
4
5
textField.rx_text
.debounce(0.3, scheduler: MainScheduler.instance) // 延遲 0.3s 後才進行事件處理
.subscribeNext{ s in
print(s)
}.addDisposableTo(disposeBag)